// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "hid-parser/item.h"

#include <string.h>
#include <zircon/assert.h>

namespace hid {
namespace impl {

Item::Tag get_main_tag(uint8_t b_tag) {
  switch (b_tag) {
    case 8:
      return Item::Tag::kInput;
    case 9:
      return Item::Tag::kOutput;
    case 10:
      return Item::Tag::kCollection;
    case 11:
      return Item::Tag::kFeature;
    case 12:
      return Item::Tag::kEndCollection;
    default:
      return Item::Tag::kReserved;
  }
}

Item::Tag get_global_tag(uint8_t b_tag) {
  switch (b_tag) {
    case 0:
      return Item::Tag::kUsagePage;
    case 1:
      return Item::Tag::kLogicalMinimum;
    case 2:
      return Item::Tag::kLogicalMaximum;
    case 3:
      return Item::Tag::kPhysicalMinimum;
    case 4:
      return Item::Tag::kPhysicalMaximum;
    case 5:
      return Item::Tag::kUnitExponent;
    case 6:
      return Item::Tag::kUnit;
    case 7:
      return Item::Tag::kReportSize;
    case 8:
      return Item::Tag::kReportId;
    case 9:
      return Item::Tag::kReportCount;
    case 10:
      return Item::Tag::kPush;
    case 11:
      return Item::Tag::kPop;
    default:
      return Item::Tag::kReserved;
  }
}

Item::Tag get_local_tag(uint8_t b_tag) {
  switch (b_tag) {
    case 0:
      return Item::Tag::kUsage;
    case 1:
      return Item::Tag::kUsageMinimum;
    case 2:
      return Item::Tag::kUsageMaximum;
    case 3:
      return Item::Tag::kDesignatorIndex;
    case 4:
      return Item::Tag::kDesignatorMinimum;
    case 5:
      return Item::Tag::kDesignatorMaximum;
    // No tag for 6.
    case 7:
      return Item::Tag::kStringIndex;
    case 8:
      return Item::Tag::kStringMinimum;
    case 9:
      return Item::Tag::kStringMaximum;
    case 10:
      return Item::Tag::kDelimiter;
    default:
      return Item::Tag::kReserved;
  }
}

// This is the bit pattern for long items which this
// library does not support.
constexpr uint8_t kLongItemMarker = 0xfe;

Item::Type get_type_and_size(uint8_t data, uint8_t* size) {
  if (data == kLongItemMarker)
    return Item::Type::kLongItem;

  // Short item.
  // Payload size is 0,1,2,4 bytes.
  auto b_size = static_cast<uint8_t>(data & 0x03);
  *size = (b_size != 3) ? b_size : 4;

  switch ((data >> 2) & 0x03) {
    case 0:
      return Item::Type::kMain;
    case 1:
      return Item::Type::kGlobal;
    case 2:
      return Item::Type::kLocal;
    default:
      return Item::Type::kReserved;
  }
}

Item::Tag get_tag(Item::Type type, uint8_t data) {
  uint8_t b_tag = (data >> 4) & 0x0f;
  switch (type) {
    case Item::Type::kMain:
      return get_main_tag(b_tag);
    case Item::Type::kGlobal:
      return get_global_tag(b_tag);
    case Item::Type::kLocal:
      return get_local_tag(b_tag);
    default:
      return Item::Tag::kReserved;
  }
}

}  // namespace impl.

Item Item::ReadNext(const uint8_t* data, size_t len, size_t* actual) {
  ZX_DEBUG_ASSERT(len != 0);

  uint8_t size = 0;

  auto type = impl::get_type_and_size(data[0], &size);
  auto tag = impl::get_tag(type, data[0]);

  // Amount to parse is 1-byte for the header and  |size| for the payload
  // for short items. Long items are not supported.
  *actual = (type != Item::Type::kLongItem) ? 1 + size : 0;

  uint32_t item_data = 0;
  if (*actual <= len) {
    for (uint8_t ix = 0; ix < size; ++ix) {
      item_data |= data[1 + ix] << (8 * ix);
    }
  }

  return Item(type, tag, size, item_data);
}

// Type punning beyond the magic 'char' type is no longer tolerated
// for example the simpler  "return *reinterpret_cast<T*>(data);"
// generates a warning even on gcc.
template <typename T>
T bit_cast(const uint32_t* data) {
  T dest;
  memcpy(&dest, data, sizeof(dest));
  return dest;
}

int32_t Item::signed_data() const {
  switch (size_) {
    case 1:
      return bit_cast<int8_t>(&data_);
    case 2:
      return bit_cast<int16_t>(&data_);
    case 4:
      return bit_cast<int32_t>(&data_);
    default:
      return 0;
  }
}

}  // namespace hid
